package com.cdream.common.idempotent.aspect;

import com.cdream.common.idempotent.annotation.Idempotent;
import com.cdream.common.idempotent.autoconfig.IdempotentProperties;
import com.cdream.common.idempotent.core.IdempotentStrategy;
import com.cdream.common.idempotent.exception.MessageConcurrencyException;
import com.cdream.common.idempotent.exception.MessageConsumedException;
import com.cdream.common.lock.core.LockInfo;
import com.cdream.common.lock.core.LockTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @author cdream
 * @date 2022/12/27
 */
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
@RequiredArgsConstructor
public class IdempotentAspect {
    private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    private static final ExpressionParser PARSER = new SpelExpressionParser();
    private final IdempotentStrategy idempotentStrategy;
    private final LockTemplate lockTemplate;
    private final IdempotentProperties idempotentProperties;
    private final BeanResolver beanResolver;

    @Pointcut("@annotation(com.cdream.common.idempotent.annotation.Idempotent)")
    public void idempotentPointCut() {
    }

    @Around("idempotentPointCut()")
    public Object idempotent(ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();
        //方法参数
        Idempotent annotation = method.getAnnotation(Idempotent.class);
        String[] definitionKeys = annotation.keys();
        if (definitionKeys.length <= 1 && "".equals(definitionKeys[0])) {
            throw new IllegalArgumentException(method.getName() + "key不能为空");
        }
        String key = getSpelDefinitionKey(target, definitionKeys, method, args);

        log.info("唯一key {}", key);
        if (idempotentStrategy.exitKey(key)) {
            log.warn("重复消费 {}", key);
            throw new MessageConsumedException(key + "已经消费");
        }
        LockInfo lock = lockTemplate.lock(key, 30000, idempotentProperties.getTryLockTime());
        if (lock == null) {
            log.info("有消息正在消费");
            // 抛出异常依赖mq自动重试
            throw new MessageConcurrencyException("有消息正在消费");
        }
        try {
            Object proceed = joinPoint.proceed();
            idempotentStrategy.save(key);
            return proceed;
        } catch (Exception e) {
            log.error("throwable ", e);
            throw new RuntimeException(e);
        } finally {
            lockTemplate.releaseLock(lock);
        }

    }

    protected String getSpelDefinitionKey(Object target, String[] definitionKeys, Method method, Object[] parameterValues) {
        StandardEvaluationContext context = new MethodBasedEvaluationContext(target, method, parameterValues, NAME_DISCOVERER);
        // 这个暂时不做bean解析，没什么卵用
        context.setBeanResolver(beanResolver);
        List<String> definitionKeyList = new ArrayList<>(definitionKeys.length);
        for (String definitionKey : definitionKeys) {
            if (definitionKey != null && !definitionKey.isEmpty()) {
                String key = PARSER.parseExpression(definitionKey).getValue(context, String.class);
                definitionKeyList.add(key);
            }
        }
        return StringUtils.collectionToDelimitedString(definitionKeyList, ".", "", "");
    }

}
